package cn.zsc.netdisk.service;

import ch.qos.logback.core.util.CloseUtil;
import cn.zsc.netdisk.common.*;
import cn.zsc.netdisk.common.Paths;
import cn.zsc.netdisk.exception.SharePasswordException;
import cn.zsc.netdisk.exception.ShareUrlException;
import cn.zsc.netdisk.pojo.Advise;
import cn.zsc.netdisk.pojo.Share;
import cn.zsc.netdisk.util.Utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author ccying
 * @date 2021/2/20
 */
@Service
@Slf4j
public class FileService {
    private final int size = 10 << 20;
    private final AdviseService adviseService;
    private final ShareService shareService;

    public FileService(AdviseService adviseService, ShareService shareService) {
        this.adviseService = adviseService;
        this.shareService = shareService;
    }

    @PostConstruct
    public void init() throws IOException {
        if (!(new File(Paths.AVATAR_ROOT_PATH).exists())) {
            log.info(String.format("创建用户后台头像存储根目录路径：【%s】", Paths.AVATAR_ROOT_PATH));
            Files.createDirectories(java.nio.file.Paths.get(Paths.AVATAR_ROOT_PATH));
        }
        if (!(new File(Paths.ROOT_PATH ).exists())) {
            log.info(String.format("创建用户后台文件存储根路径：【%s】", Paths.ROOT_PATH));
            Files.createDirectories(java.nio.file.Paths.get(Paths.ROOT_PATH));
        }
        if (!(new File(Paths.TEMP_ROOT_PATH_DOWN).exists())) {
            log.info(String.format("创建用户后台下载文件存储根路径：【%s】", Paths.TEMP_ROOT_PATH_DOWN));
            Files.createDirectories(java.nio.file.Paths.get(Paths.TEMP_ROOT_PATH_DOWN));
        }
        if (!(new File(Paths.TEMP_ROOT_PATH_SHARE).exists())) {
            log.info(String.format("创建用户后台分享文件存储根路径：【%s】", Paths.TEMP_ROOT_PATH_SHARE));
            Files.createDirectories(java.nio.file.Paths.get(Paths.TEMP_ROOT_PATH_SHARE));
        }
    }

    /**
     * 请求获取当前用户根目录存储文件
     * @return 文件数组
     * */
    public cn.zsc.netdisk.pojo.File[] getCurrentRootPathFiles() throws IOException {
        log.info("获取当前用户根目录存储文件");
        String uid = UserInfo.getPrincipal();
        Paths.current_root_path = Paths.ROOT_PATH + uid;
        if (!(new File(Paths.current_root_path).exists())) {
            log.info(String.format("创建用户存储根目录【%s】", Paths.current_root_path));
            Files.createDirectories(java.nio.file.Paths.get(Paths.current_root_path));
        }
        log.info(String.format("当前用户根目录：%s", Paths.current_root_path));
        return this.getCurrentPathFiles(uid);
    }

    /**
     * 获取给定路径存储文件
     * @param path 给定文件存储路径字符串
     * @return 文件数组
     * */
    public cn.zsc.netdisk.pojo.File[] getCurrentPathFiles(String path) throws IOException {
        path = Paths.ROOT_PATH + path;
        log.info(String.format("获取给定路径存储文件，给定路径：%s", path));
        java.io.File file = new java.io.File(path);
        Paths.current_path = path;
        log.info(String.format("当前存储路径：%s", Paths.current_path));
        String[] filenames = file.list();
        cn.zsc.netdisk.pojo.File[] files = new cn.zsc.netdisk.pojo.File[filenames.length];
        for (int i = 0; i < files.length; i++) {
            //将java.io.File转化成cn.zsc.netdisk.pojo.File
            files[i] = this.conver(Paths.current_path + File.separator + filenames[i]);
        }
        //对文件数组进行排序，文件夹在前，文件在后
        Arrays.sort(files, (o1, o2) -> {
            if (o1.getType().equals(o2.getType())) {
                return o1.getFilename().compareTo(o2.getFilename());
            } else {
                return o1.getType().compareTo(o2.getType());
            }
        });
        return files;
    }

    /**
     * 获取给定路径的文件夹名
     * @param path 给定抽象路径字符串
     * */
    public List<String> getCurrentDirName(String path) {
        path = Paths.ROOT_PATH + path;
        log.info(String.format("获取给定路径【%s】的文件夹名", path));
        File root = new File(path);
        String[] files = root.list();
        List<String> list = new ArrayList<>(files.length);
        for (String f : files) {
            String filePath = path + File.separator + f;
            File file = new File(filePath);
            if (file.isDirectory()) {
                list.add(f);
            }
        }
        return list;
    }

    /**
     * 传输图片
     * @param path 给定图片文件抽象路径字符串
     * @param type 传输类型
     * @param response http响应
     * */
    public void getImage(String path, int type, HttpServletResponse response)
            throws IOException{
        if (type == GetImageType.avatar.getType()) {
            path = Paths.AVATAR_ROOT_PATH + path;
        } else if (type == GetImageType.file.getType()) {
            path = Paths.current_path + File.separator + path;
        }
        response.setContentType("image/png");
        java.io.File file = new java.io.File(path);
        FileInputStream fis = new FileInputStream(file);
        OutputStream os = response.getOutputStream();
        //图片一般不是很大
        byte[] bytes = new byte[fis.available()];
        fis.read(bytes);
        os.write(bytes);
        os.flush();
        Utils.close(os, fis);
    }

    /**
     * 保存文件
     * @param parentPath 文件保存路径父路径
     * @param files 文件数组
     * */
    public void saveFile(String parentPath, MultipartFile[] files) throws IOException {
        for (MultipartFile file : files) {
            String path = parentPath + File.separator + file.getOriginalFilename();
            this.saveFile(path, file);
        }
    }

    /**
     * 保存文件
     * @param path 文件保存路径
     * @param multipartFile 文件
     * @throws IOException
     * */
    public void saveFile(String path, MultipartFile multipartFile) throws IOException {
        log.info(String.format("保存文件【%s】", path));
        File file = new File(path);
        InputStream is = multipartFile.getInputStream();
        OutputStream os = new FileOutputStream(file);
        byte[] bytes = new byte[size];
        int len = 0;
        while ((len = is.read(bytes)) != -1) {
            os.write(bytes, 0, len);
            os.flush();
        }
        Utils.close(os, is);
    }

    /**
     * 下载文件
     * @param request http请求
     * @param path 给定下载文件抽象路径字符串
     * @param type 下载类型：0-下载存储文件，1-下载分享文件
     * @return
     * */
    public ResponseEntity<byte[]> download(HttpServletRequest request, String path, int type)
            throws Exception {
        if (type == DownType.FILE_DOWN.get()) {
            path = Paths.TEMP_ROOT_PATH_DOWN + path;
        } else if (type == DownType.SHARE_DOWN.get()) {
            path = Paths.TEMP_ROOT_PATH_SHARE + path;
        }
        log.info(String.format("下载文件，要下载文件路径：%s", path));
        File file = new File(path);
        path = this.getFilename(request, path);
        HttpHeaders headers = new HttpHeaders();
        headers.setContentDispositionFormData("attachment", new File(path).getName());
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        return new ResponseEntity<>(FileUtils.readFileToByteArray(file), headers, HttpStatus.OK);
    }

    /**
     * 新建文件夹
     * @param path 给定要新建文件夹的抽象路径字符串
     * */
    public void createDirectory(String path) throws IOException {
        path = Paths.ROOT_PATH + path + "/新建文件夹";
        log.info(String.format("新建文件夹，文件夹路径：%s", path));
        Files.createDirectory(java.nio.file.Paths.get(path));
    }

    /**
     * 移动文件
     * @param sources 给定要移动文件名数组
     * @param target 移动目标路径名
     * @throws IOException
     * */
    public void move(String[] sources, String target) throws IOException {
        for (String source : sources) {
            String file  = Paths.ROOT_PATH + source;
            String newFile = Paths.ROOT_PATH + target + File.separator + new File(file).getName();
            log.info(String.format("移动文件【%s】->【%s】", file, newFile));
            Files.move(java.nio.file.Paths.get(file), java.nio.file.Paths.get(newFile), StandardCopyOption.REPLACE_EXISTING);
        }
    }

    /**
     * 删除存储文件
     * @throws IOException
     * @param paths 给定要删除文件抽象路径字符串数组
     * */
    public void deleteFile(String[] paths) throws IOException {
        for (String path : paths) {
            path = Paths.ROOT_PATH + path;
            this.deleteFile(path);
        }
    }

    /**
     * 删除存储文件
     * @param path 给定要删除文件抽象路径字符串
     * */
    public void deleteFile(String path) throws IOException {
        log.info(String.format("删除存储文件，文件路径：%s", path));
        Files.walkFileTree(java.nio.file.Paths.get(path), new SimpleFileVisitor<Path>(){
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) throws IOException {
                Files.delete(file);
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                if (exc != null) {
                    throw  exc;
                }
                Files.delete(dir);
                return FileVisitResult.CONTINUE;
            }
        });
    }

    /**
     * 分享文件
     * @param name 分享文件压缩后路径名
     * @throws Exception
     * */
    public void shareFile(String name) throws Exception {
        name = Paths.TEMP_ROOT_PATH_SHARE + name;
        log.info(String.format("分享文件:%s", name));
        //生出文件分享链接
        Share share = this.shareService.to(name);
        //添加文件分享链接记录
        this.shareService.add(share);
        //生成通知
        Advise advise = this.adviseService.to(UserInfo.getPrincipal(), AdviseType.SHARE_FILE, share);
        //添加通知记录
        this.adviseService.add(advise);
    }

    /**
     * 验证url和密码是否正确
     * @param url 分享链接url
     * @param password 分享链接密码
     * @return 分享文件名
     * */
    public String verify(String url, int password) throws ShareUrlException, SharePasswordException {
        log.info("验证url和密码是否正确");
        return this.shareService.verify(url, password);
    }

    /**
     * 重命名存储文件
     * @param source 原来文件名表示的抽象路径字符串
     * @param target 目标文件名表示的抽象路径字符串
     * @throws IOException
     * */
    public void renameFile(String source, String target) throws IOException {
        source = Paths.ROOT_PATH + source;
        target = Paths.ROOT_PATH + target;
        log.info(String.format("重命名文件:【%s】=>【%s】", source, target));
        Files.move(java.nio.file.Paths.get(source), java.nio.file.Paths.get(target));
    }

    /**
     * 搜索文件名包含关键字的文件
     * @param key 关键字
     * @return 文件
     * */
    public cn.zsc.netdisk.pojo.File[] searchFile(String key) throws IOException {
        log.info("搜索文件名包含关键字的文件");
        String[] paths = Files.find(java.nio.file.Paths.get(Paths.current_root_path), Integer.MAX_VALUE, (path, attributes) ->
            !attributes.isDirectory() && path.toString().contains(key)).map(path -> path.toString()).toArray(String[]::new);
        //将存储系统的文件转换成本系统文件实体对象
        cn.zsc.netdisk.pojo.File[] files = new cn.zsc.netdisk.pojo.File[paths.length];
        for (int i = 0; i < files.length; i++) {
            files[i] = this.conver(paths[i]);
        }
        return files;
    }

    /**
     * 将文件转换成POJO
     * @param file 文件
     * @return POJO File
     * */
    private cn.zsc.netdisk.pojo.File conver(File file) throws IOException {
        cn.zsc.netdisk.pojo.File pojo = new cn.zsc.netdisk.pojo.File();
        //filename
        String filename = file.getName();
        pojo.setFilename(filename);
        //path
        String path = file.getParent().substring(Paths.ROOT_PATH.length());
        pojo.setPath(path);
        //type
        if (file.isDirectory()) {
            pojo.setType(FileType.DIRECTORY.getType());
        } else {
            int index = filename.lastIndexOf('.');
            if (index != -1) {
                String suffix = filename.substring((index + 1)).toLowerCase();
                if ("jpg".equals(suffix) || "png".equals(suffix)) {
                    pojo.setType(FileType.IMAGE_FILE.getType());
                } else {
                    pojo.setType(FileType.FILE.getType());
                }
            } else {
                pojo.setType(FileType.FILE.getType());
            }
        }
        //size
        if (file.isDirectory()) {
            pojo.setSize("-");
        } else {
            long bytes = file.length();
            long kb = bytes / 1024;
            String size = kb + "KB";
            pojo.setSize(size);
            long mb = kb / 1024;
            if (mb > 0) {
                size = mb + "MB";
                pojo.setSize(size);
            }
            long gb = mb / 1024;
            if (gb > 0) {
                size = gb + "GB";
                pojo.setSize(size);
            }
        }
        //modified
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String time = format.format(new Date(file.lastModified()));
        pojo.setModified(time);
        return pojo;
    }
    /**
     * 将文件转换成POJO
     * @param path 给定后台文件抽象路径字符串
     * @return POJO File
     * */
    private cn.zsc.netdisk.pojo.File conver(String path) throws IOException {
        File file = new File(path);
        return this.conver(file);
    }

    /**
     * 压缩文件
     * @param sources 给定要压缩文件抽象路径字符串数组
     * @param target 给定压缩文件输出抽象路径字符串
     * @throws IOException
     * @return 压缩文件输出抽象路径字符串
     * */
    public String toZip(String[] sources, String target, int type) throws IOException {
        final int available = 5 << 10;
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(target));
        for (String source : sources) {
            source = Paths.ROOT_PATH + source;
            log.info("压缩文件，要压缩文件路径名：" + source);
            File file = new File(source);
            if (file.isDirectory()) {
                //要压缩文件是文件夹
                int index = source.lastIndexOf('/');
                Files.walkFileTree(java.nio.file.Paths.get(source),
                        new SimpleFileVisitor<Path>() {
                    @Override
                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                            throws IOException {
                        ZipEntry entry = new ZipEntry(file.toString().substring(index + 1));
                        zos.putNextEntry(entry);

                        FileInputStream fis = new FileInputStream(file.toString());
                        int len = 0;
                        byte[] bytes = new byte[available];
                        while ((len = fis.read(bytes)) != -1) {
                            zos.write(bytes, 0, len);
                            zos.flush();
                        }
                        return FileVisitResult.CONTINUE;
                    }

                    @Override
                    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                            throws IOException {
                        ZipEntry entry = new ZipEntry(dir.toString().substring(index + 1)
                                + File.separator);
                        zos.putNextEntry(entry);
                        return FileVisitResult.CONTINUE;
                    }
                });
            } else {
                //要压缩文件是文件
                ZipEntry entry = new ZipEntry(file.getName());
                zos.putNextEntry(entry);

                FileInputStream fis = new FileInputStream(file);
                int len = 0;
                byte[] bytes = new byte[available];
                while ((len = fis.read(bytes)) != -1) {
                    zos.write(bytes, 0, len);
                    zos.flush();
                }
            }
        }
        zos.close();
        log.info("压缩文件输出文件路径名：" + target);
        if (type == ZipType.DOWN_ZIP.get()) {
            //下载压缩
            target = target.substring(Paths.TEMP_ROOT_PATH_DOWN.length());
        } else if (type == ZipType.SHARE_ZIP.get()) {
            //分享压缩
            target = target.substring(Paths.TEMP_ROOT_PATH_SHARE.length());
        }
        return target;
    }

    /**
     * 压缩文件
     * @param sources 给定要压缩文件抽象路径字符串数组
     * @param type 0-下载压缩，1-分享压缩
     */
    public String toZip(String[] sources, int type) throws IOException {
        if (sources == null) {
            throw new IOException("压缩文件为空");
        }
        File file = new File(sources[0]);
        String target = null;
        if (type == ZipType.DOWN_ZIP.get()) {
           target  = Paths.TEMP_ROOT_PATH_DOWN + file.getName() + ".zip";
        } else if (type == ZipType.SHARE_ZIP.get()) {
            target  = Paths.TEMP_ROOT_PATH_SHARE + file.getName() + ".zip";
        }
        return this.toZip(sources, target, type);
    }

    /**
     * 根据浏览器的不同进行编码设置
     * @param request http请求
     * @param filename 文件名
     * @throws Exception
     * @return 编码后的文件名
     * */
    private String getFilename(HttpServletRequest request, String filename) throws Exception{
        //IE不同版本User-Agent中出现的关键词
        String[] ieBrowserKeyWorld = {"MSIE", "Trident", "Edge"};
        //获取请求头代理信息
        String userAgent = request.getHeader("User-Agent");
        for (String keyWorld : ieBrowserKeyWorld) {
            if (userAgent.contains(keyWorld)) {
                return URLEncoder.encode(filename, "UTF-8").replace("+", " ");
            }
        }
        //其他浏览器
        return new String(filename.getBytes("UTF-8"), "ISO-8859-1");
    }
}
